#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include "object.h"
#include "util.h"
#include "amazon.h"
#include "cloud_sync.h"
#include "object_dir.h"

/*
  Some method to come up with the device id string for each device
*/
int whats_my_id () {
  //TODO
  return 1;
}


/*
  Converts the string of length LEN to integer
*/
int string_to_int (char *s, int len) {
  int ret = 0, i;
  for (i=0; i<len; i++) {
    ret = ret*10 + (((int)s[i])-48);
  }
  return ret;
}

/* 
  Converts integer to string of length LEN (padded with zeroes on left)
*/
void int_to_string (int inp, char *s, int len) {
  memset (s, '0', sizeof (char)*len);
  int i=len-1;
  while (inp > 0 && i>=0) {
    s[i] = inp%10 + 48;
    inp = inp/10;
    i--;
  }
}

/*
  Since there is no need for anything on FUSE side to deal with 
  String representation, these functions better be not exposed to the
  world.
*/

/* 
  Converts an object_id to string. The string should be of length
  TYPELEN+1+IDLEN+1+VERLEN+1+DEVLEN+1
  (The intervening 1's are for '-', while the last one is for the NULL
*/
void object_id_to_string (char *str, object_id *id) {

  /* This would change in case typelen is changed  */
  switch (id->type) {
    case DATA_OBJ:
      str[0] = 'r'; break;
    case FILE_OBJ:
      str[0] = 'f'; break;
    case DIR_OBJ:
      str[0] = 'd'; break;
    default:
      assert (0);
  }
  strncpy (str+TYPELEN+1, id->id, IDLEN); 
  int_to_string (id->ver, str+TYPELEN+1+IDLEN+1, VERLEN);
  int_to_string (id->dev, str+TYPELEN+1+IDLEN+1+VERLEN+1, DEVLEN);
  str[TYPELEN] = str[TYPELEN+1+IDLEN] = str[TYPELEN+1+IDLEN+1+VERLEN] = '-';
  str[TYPELEN+1+IDLEN+1+VERLEN+1+DEVLEN] = '\0';
}

/* Converts a given string to object_id */
void string_to_object_id (object_id *id, char *str) {
  /* Don't know this would be needed or not, but there should 
     be a dual of the function above. :D :D */
 
  memset (id, 0, sizeof (object_id)); 
  /* This would change in case typelen is changed, switch case may not work
     at all  */
  switch (str[0]) {
    case 'r':
      id->type = DATA_OBJ; break;
    case 'f':
      id->type = FILE_OBJ; break;
    case 'd':
      id->type = DIR_OBJ; break;
    default:
      assert (0);
  }
  strncpy (id->id, str+TYPELEN+1, IDLEN);
  id->ver = string_to_int (str+TYPELEN+1+IDLEN+1, VERLEN);  
  id->dev = string_to_int (str+TYPELEN+1+IDLEN+1+VERLEN+1, DEVLEN);
}

/* 
  Returns the latest available version of a particular object. If no such
  object exists, the version (and device) is set to 0, otherwise set 
  appropriately
*/

#if 0

void get_latest_object_id (object_id *id, object_id *latest) {

  //TODO: Amazon implementation
  char s[ID_STRING_LENGTH];
  object_id_to_string (s, id);

  DIR *dp; 
  dp = opendir (PREFIX);
  assert (dp != NULL);
  struct dirent *dent;
  dent = readdir (dp);
  while (dent != NULL) {
    if (strncmp (dent->d_name, s, TYPELEN+1+IDLEN+1) == 0) {
      /* Only if the TYPE and ID match, we enter this */
      /* Hopefully dent->d_name ends with a NULL */
      if (strncmp (dent->d_name, s, ID_STRING_LENGTH) > 0) 
        strncpy (s, dent->d_name, ID_STRING_LENGTH);
    }
    dent = readdir (dp);
  }
  closedir (dp);

  /* 
    Now comes the cloud part:
    If we didn't get any object in the local cache directory,
    we better look at the cloud now.

    Also, if we infact did find one object, and it is too old 
    (which we can find out from the last modification time), it
    may be a stale version. We need to check in that case too.
  */

  char s_[ID_STRING_LENGTH];
  object_id_to_string (s_, id); 

  int need_to_search_cloud = 0;

  if (strcmp (s, s_) == 0) {
    /* We didn't find anything in the local disk cache */
    need_to_search_cloud = 1;
  } else {
    /* We found something in the local disk cache. */

    char fullpathname[200];
    sprintf (fullpathname, "%s/%s", PREFIX, s);

    struct stat b;
    stat (fullpathname, &b);

    time_t cur_time;
    time (&cur_time);

    /* 5 min stale period */
    if (cur_time - b.st_mtime > 300)
      need_to_search_cloud = 1;

  }

  if (need_to_search_cloud) {

    char prefix[TYPELEN+1+IDLEN+1];
    strncpy (prefix, s, TYPELEN+1+IDLEN);
    prefix[TYPELEN+1+IDLEN] = '\0';

    void *cloud_objects_list = malloc (10000*ID_STRING_LENGTH);
    
    int count = list_cloud_object (prefix, 10000, cloud_objects_list);

    /* Now do the actual comparison */
    int i=0;

    for (i=0; i<count; i++) {
      if (strncmp (cloud_objects_list + (i*ID_STRING_LENGTH), s, 
                                      ID_STRING_LENGTH) > 0) 
        strncpy (s, cloud_objects_list + (i*ID_STRING_LENGTH), 
                                      ID_STRING_LENGTH);
    }

    free (cloud_objects_list);
  }

  string_to_object_id (latest, s);

}

#endif

void get_latest_object_id (object_id *id, object_id *latest) {

  char s[ID_STRING_LENGTH];
  object_id_to_string (s, id);

  char s_[TYPELEN+1+IDLEN+1];
  strncpy (s_, s, TYPELEN+1+IDLEN);
  s_[TYPELEN+1+IDLEN] = '\0';

  char *list = malloc (ID_STRING_LENGTH*10000);
  int c = prefix_search (s_, ID_STRING_LENGTH*10000, list);

  int i;

  for (i=0; i<c; i++) { 
      if (strncmp (list+(i*ID_STRING_LENGTH), s, ID_STRING_LENGTH) > 0) 
        strncpy (s, list+(i*ID_STRING_LENGTH), ID_STRING_LENGTH);
  }

  string_to_object_id (latest, s);

}

int get_object (object_id *obj_id, object *obj, 
                    size_t max_length, char *buf) {

  //TODO: Amazon implementation

  char object_name[ID_STRING_LENGTH];

  /* Some debugging thing */
  object_id_to_string (object_name, obj_id);

  if (obj_id->ver == 0 && obj_id->dev == 0) {
    /* We need to fetch the latest version */
    object_id latest;
    get_latest_object_id (obj_id, &latest);
    object_id_to_string (object_name, &latest);   
  } else {
    /* Some specific version is requested. This is not the 
       normal operation, but let's implement this for getting
       old version's of objects */
    object_id_to_string (object_name, obj_id);
  }
 
  debug("GET %s\n", object_name);

  assert ((obj_id->key).key[0] != 'h') ;
  char filename[200+ID_STRING_LENGTH];
  sprintf (filename, "%s%s", PREFIX, object_name);
  
  int fd = open (filename, O_RDONLY);

  if (fd == -1) {
    /* Let's get the object from the cloud */
    get_cloud_object (object_name, &(obj_id->key));
    set_disk_field (object_name, 1); /* It's already in the object_dir */
    fd = open (filename, O_RDONLY);
    assert (fd != -1);
  }

  /* Verify the key from the first 64 bit of the disk-object */
  unsigned char k1[32], k2[32];
  read (fd, k1, 32);
  read (fd, k2, 32);
  assert (strncmp ((char *)(obj_id->key).key, (char *)k1, 32) == 0);
  assert (strncmp ((char *)(obj_id->key).iv, (char *)k2, 32) == 0);


  int len = read (fd, buf, max_length);   /* deduce the actual length */
  assert (len != -1); /* Error free operation */
  /* To find whether EOF is reached, read 1 more char */
  char tmp;
  int len_1 = read (fd, &tmp, 1);
  close (fd);

  /* Return -1 in case the file didn't end */
  if (len_1 != 0)
    return -1;

  obj->type = obj_id->type;
  obj->length = len;
  obj->data = buf;

  return 0; 
}

void put_object (object_id *obj_id, object *obj) {
  //TODO: Amazon implementation

  assert (obj_id->ver == 0 && obj_id->dev == 0);
  assert (obj_id->type == obj->type);
  /*
    It could be a new object, or a newer version of an existing
    object. Let's check first, and come up with the name with
    which this object should be written 
  */

  object_id latest;
  get_latest_object_id (obj_id, &latest);

  /* A small test could be put in here to find if it is a new object */
  latest.ver++; latest.dev = whats_my_id ();

  char object_name[ID_STRING_LENGTH];
  object_id_to_string (object_name, &latest);

  char filename[200+ID_STRING_LENGTH];
  sprintf (filename, "%s%s", PREFIX, object_name);

  debug ("PUT %s\n", object_name);
  int fd = open (filename, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);
  assert (fd != -1);  /* Should not happen ever */

  /* Write the key here */
  write (fd, (obj_id->key).key, 32);
  write (fd, (obj_id->key).iv, 32);

  /* Let's make a policy that the object which is of size 0 in 
     length is stored unencrypted => simplifies things
  */
  if (obj->length !=0) {
    size_t len = (size_t) write (fd, obj->data, obj->length);
    assert (len == obj->length);
  }
  close (fd);
  
  add_to_object_dir (object_name); 
  set_disk_field (object_name, 1);
  push_to_sync_queue (object_name);
}
